#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <errno.h>

#include <include/pf_internal.h>
#include <include/pf_sha1.h>

#define    K(t)    _K[(t) / 20]

#define    F0(b, c, d)    (((b) & (c)) | ((~(b)) & (d)))
#define    F1(b, c, d)    (((b) ^ (c)) ^ (d))
#define    F2(b, c, d)    (((b) & (c)) | ((b) & (d)) | ((c) & (d)))
#define    F3(b, c, d)    (((b) ^ (c)) ^ (d))

#define    S(n, x)        (((x) << (n)) | ((x) >> (32 - n)))

#define    H(n)    (sha1->h.b32[(n)])
#define    COUNT    (sha1->count)
#define    BCOUNT    (sha1->c.b64[0] / 8)
#define    W(n)    (sha1->m.b32[(n)])

#define    PUTBYTE(x)    { \
    sha1->m.b8[(COUNT % 64)] = (x);        \
    COUNT++;                \
    COUNT %= 64;                \
    sha1->c.b64[0] += 8;            \
    if (COUNT % 64 == 0)            \
        pf_sha1_step(sha1);        \
     }

#define    PUTPAD(x)    { \
    sha1->m.b8[(COUNT % 64)] = (x);        \
    COUNT++;                \
    COUNT %= 64;                \
    if (COUNT % 64 == 0)            \
        pf_sha1_step(sha1);        \
     }

struct pf_sha1 
{
    union 
    {
        uint8_t    b8[20];
        uint32_t   b32[5];
    } h;
    union 
    {
        uint8_t    b8[8];
        uint64_t   b64[1];
    } c;
    union 
    {
        uint8_t    b8[64];
        uint32_t   b32[16];
    } m;
    uint8_t        count;
};

static const  u_int32_t _K[] = { 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6 };

/*********************************************************************************************
Function Name:  pf_sha1_step
Description  :  Step compute the sha result.
Inputs       :  pf_sha1_t* sha1               : 
Outputs      :  
ErrorCodes   :  
History      :
---------------------------------------------------------------------------------------------
Author                 Date                          Comments
huyq                   2011-06-23                    create
**********************************************************************************************/
void pf_sha1_step(pf_sha1_t* sha1);

/*********************************************************************************************
Function Name:  pf_sha1_pad
Description  :  Pad sha1 context
Inputs       :  pf_sha1_t* sha1               : 
Outputs      :  
ErrorCodes   :  
History      :
---------------------------------------------------------------------------------------------
Author                 Date                          Comments
huyq                   2011-06-23                    create
**********************************************************************************************/
void pf_sha1_pad(pf_sha1_t* sha1);

/********************************************************************************************/

pf_sha1_t* pf_sha1_start(void)
{
    pf_sha1_t* sha1 = malloc(sizeof(pf_sha1_t));

    if(!sha1)
    {
        pf_set_errno(PF_ENOMEM);
        return NULL;
    }

    memset(sha1, 0 , sizeof(pf_sha1_t));
    H(0) = 0x67452301;
    H(1) = 0xefcdab89;
    H(2) = 0x98badcfe;
    H(3) = 0x10325476;
    H(4) = 0xc3d2e1f0;

    return sha1;
}

int pf_sha1_append(pf_sha1_t* sha1, const void* data, size_t len)
{
    size_t gap_len        = 0;
    size_t gap_start      = 0;
    size_t off            = 0;
    size_t copy_size      = 0;
    const u_int8_t* input = NULL;

    assert(sha1);
    assert(data);

    input = (const u_int8_t *)data;
    off = 0;
    while(off < len)
    {
        gap_start = COUNT % 64;
        gap_len = 64 - gap_start;

        copy_size = (gap_len < len - off) ? gap_len : len - off;
        memcpy(&sha1->m.b8[gap_start], &input[off], copy_size);
        COUNT += copy_size;
        COUNT %= 64;
        sha1->c.b64[0] += copy_size * 8;
        if(COUNT % 64 == 0)
        {
            pf_sha1_step(sha1);
        }
        off += copy_size;
    }

    return 0;
}

int pf_sha1_finish(pf_sha1_t* sha1,
                   unsigned char result[20])
{
    assert(sha1);

    pf_sha1_pad(sha1);
#ifdef WORDS_BIGENDIAN
    memcpy(result,&sha1->h.b8[0], 20);
#else
    result[0]  = sha1->h.b8[3];
    result[1]  = sha1->h.b8[2];
    result[2]  = sha1->h.b8[1];
    result[3]  = sha1->h.b8[0];
    result[4]  = sha1->h.b8[7];
    result[5]  = sha1->h.b8[6];
    result[6]  = sha1->h.b8[5];
    result[7]  = sha1->h.b8[4];
    result[8]  = sha1->h.b8[11];
    result[9]  = sha1->h.b8[10];
    result[10] = sha1->h.b8[9];
    result[11] = sha1->h.b8[8];
    result[12] = sha1->h.b8[15];
    result[13] = sha1->h.b8[14];
    result[14] = sha1->h.b8[13];
    result[15] = sha1->h.b8[12];
    result[16] = sha1->h.b8[19];
    result[17] = sha1->h.b8[18];
    result[18] = sha1->h.b8[17];
    result[19] = sha1->h.b8[16];
#endif
    if(sha1)
    {
        free(sha1);
    }

    return 0;
}

/********************************************************************************************/
 
void pf_sha1_step(pf_sha1_t* sha1)
{

    uint32_t a   = 0;
    uint32_t b   = 0;
    uint32_t c   = 0;
    uint32_t d   = 0;
    uint32_t e   = 0;
    size_t t     = 0;
    size_t s     = 0;
    uint32_t tmp = 0;
#ifndef WORDS_BIGENDIAN
    pf_sha1_t tsha1;
#endif

    assert(sha1);

#ifndef WORDS_BIGENDIAN
    memcpy(&tsha1.m.b8[0], &sha1->m.b8[0], 64);
    sha1->m.b8[0]  = tsha1.m.b8[3]; 
    sha1->m.b8[1]  = tsha1.m.b8[2];
    sha1->m.b8[2]  = tsha1.m.b8[1]; 
    sha1->m.b8[3]  = tsha1.m.b8[0];
    sha1->m.b8[4]  = tsha1.m.b8[7]; 
    sha1->m.b8[5]  = tsha1.m.b8[6];
    sha1->m.b8[6]  = tsha1.m.b8[5]; 
    sha1->m.b8[7]  = tsha1.m.b8[4];
    sha1->m.b8[8]  = tsha1.m.b8[11]; 
    sha1->m.b8[9]  = tsha1.m.b8[10];
    sha1->m.b8[10] = tsha1.m.b8[9]; 
    sha1->m.b8[11] = tsha1.m.b8[8];
    sha1->m.b8[12] = tsha1.m.b8[15]; 
    sha1->m.b8[13] = tsha1.m.b8[14];
    sha1->m.b8[14] = tsha1.m.b8[13]; 
    sha1->m.b8[15] = tsha1.m.b8[12];
    sha1->m.b8[16] = tsha1.m.b8[19]; 
    sha1->m.b8[17] = tsha1.m.b8[18];
    sha1->m.b8[18] = tsha1.m.b8[17]; 
    sha1->m.b8[19] = tsha1.m.b8[16];
    sha1->m.b8[20] = tsha1.m.b8[23]; 
    sha1->m.b8[21] = tsha1.m.b8[22];
    sha1->m.b8[22] = tsha1.m.b8[21]; 
    sha1->m.b8[23] = tsha1.m.b8[20];
    sha1->m.b8[24] = tsha1.m.b8[27]; 
    sha1->m.b8[25] = tsha1.m.b8[26];
    sha1->m.b8[26] = tsha1.m.b8[25]; 
    sha1->m.b8[27] = tsha1.m.b8[24];
    sha1->m.b8[28] = tsha1.m.b8[31]; 
    sha1->m.b8[29] = tsha1.m.b8[30];
    sha1->m.b8[30] = tsha1.m.b8[29]; 
    sha1->m.b8[31] = tsha1.m.b8[28];
    sha1->m.b8[32] = tsha1.m.b8[35]; 
    sha1->m.b8[33] = tsha1.m.b8[34];
    sha1->m.b8[34] = tsha1.m.b8[33]; 
    sha1->m.b8[35] = tsha1.m.b8[32];
    sha1->m.b8[36] = tsha1.m.b8[39]; 
    sha1->m.b8[37] = tsha1.m.b8[38];
    sha1->m.b8[38] = tsha1.m.b8[37]; 
    sha1->m.b8[39] = tsha1.m.b8[36];
    sha1->m.b8[40] = tsha1.m.b8[43]; 
    sha1->m.b8[41] = tsha1.m.b8[42];
    sha1->m.b8[42] = tsha1.m.b8[41]; 
    sha1->m.b8[43] = tsha1.m.b8[40];
    sha1->m.b8[44] = tsha1.m.b8[47]; 
    sha1->m.b8[45] = tsha1.m.b8[46];
    sha1->m.b8[46] = tsha1.m.b8[45]; 
    sha1->m.b8[47] = tsha1.m.b8[44];
    sha1->m.b8[48] = tsha1.m.b8[51]; 
    sha1->m.b8[49] = tsha1.m.b8[50];
    sha1->m.b8[50] = tsha1.m.b8[49]; 
    sha1->m.b8[51] = tsha1.m.b8[48];
    sha1->m.b8[52] = tsha1.m.b8[55]; 
    sha1->m.b8[53] = tsha1.m.b8[54];
    sha1->m.b8[54] = tsha1.m.b8[53]; 
    sha1->m.b8[55] = tsha1.m.b8[52];
    sha1->m.b8[56] = tsha1.m.b8[59]; 
    sha1->m.b8[57] = tsha1.m.b8[58];
    sha1->m.b8[58] = tsha1.m.b8[57]; 
    sha1->m.b8[59] = tsha1.m.b8[56];
    sha1->m.b8[60] = tsha1.m.b8[63]; 
    sha1->m.b8[61] = tsha1.m.b8[62];
    sha1->m.b8[62] = tsha1.m.b8[61]; 
    sha1->m.b8[63] = tsha1.m.b8[60];
#endif

    a = H(0); 
    b = H(1); 
    c = H(2); 
    d = H(3); 
    e = H(4);

    for(t = 0; t < 20; t++)
    {
        s = t & 0x0f;
        if(t >= 16)
        {
            W(s) = S(1, W((s+13) & 0x0f) ^ W((s+8) & 0x0f) ^ W((s+2) & 0x0f) ^ W(s));
        }
        tmp = S(5, a) + F0(b, c, d) + e + W(s) + K(t);
        e = d; 
        d = c; 
        c = S(30, b); 
        b = a; 
        a = tmp;
    }
    for(t = 20; t < 40; t++)
    {
        s = t & 0x0f;
        W(s) = S(1, W((s+13) & 0x0f) ^ W((s+8) & 0x0f) ^ W((s+2) & 0x0f) ^ W(s));
        tmp = S(5, a) + F1(b, c, d) + e + W(s) + K(t);
        e = d; 
        d = c; 
        c = S(30, b); 
        b = a; 
        a = tmp;
    }
    for(t = 40; t < 60; t++)
    {
        s = t & 0x0f;
        W(s) = S(1, W((s+13) & 0x0f) ^ W((s+8) & 0x0f) ^ W((s+2) & 0x0f) ^ W(s));
        tmp = S(5, a) + F2(b, c, d) + e + W(s) + K(t);
        e = d; 
        d = c; 
        c = S(30, b); 
        b = a; 
        a = tmp;
    }
    for(t = 60; t < 80; t++)
    {
        s = t & 0x0f;
        W(s) = S(1, W((s+13) & 0x0f) ^ W((s+8) & 0x0f) ^ W((s+2) & 0x0f) ^ W(s));
        tmp = S(5, a) + F3(b, c, d) + e + W(s) + K(t);
        e = d; 
        d = c; 
        c = S(30, b); 
        b = a; 
        a = tmp;
    }

    H(0) = H(0) + a;
    H(1) = H(1) + b;
    H(2) = H(2) + c;
    H(3) = H(3) + d;
    H(4) = H(4) + e;

    memset(&sha1->m.b8[0], 0 , 64);

    return;
}

void pf_sha1_pad(pf_sha1_t* sha1)
{

    size_t pad_len   = 0;        /* pad length in bytes */
    size_t pad_start = 0;

    assert(sha1);
    PUTPAD(0x80);

    pad_start = COUNT % 64;
    pad_len = 64 - pad_start;
    if(pad_len < 8)
    {
        memset(&sha1->m.b8[pad_start], 0, pad_len);
        COUNT += pad_len;
        COUNT %= 64;
        pf_sha1_step(sha1);
        pad_start = COUNT % 64;      /* should be 0 */
        pad_len = 64 - pad_start;    /* should be 64 */
    }
    memset(&sha1->m.b8[pad_start], 0, pad_len - 8);
    COUNT += (pad_len - 8);
    COUNT %= 64;
#ifdef WORDS_BIGENDIAN
    PUTPAD(sha1->c.b8[0]); 
    PUTPAD(sha1->c.b8[1]);
    PUTPAD(sha1->c.b8[2]); 
    PUTPAD(sha1->c.b8[3]);
    PUTPAD(sha1->c.b8[4]); 
    PUTPAD(sha1->c.b8[5]);
    PUTPAD(sha1->c.b8[6]); 
    PUTPAD(sha1->c.b8[7]);
#else
    PUTPAD(sha1->c.b8[7]); 
    PUTPAD(sha1->c.b8[6]);
    PUTPAD(sha1->c.b8[5]); 
    PUTPAD(sha1->c.b8[4]);
    PUTPAD(sha1->c.b8[3]); 
    PUTPAD(sha1->c.b8[2]);
    PUTPAD(sha1->c.b8[1]); 
    PUTPAD(sha1->c.b8[0]);
#endif

    return;
}
